package com.ilikesoup.instant.compare;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;

public class CompareResult<L, R> {
    private final L left;
    private final R right;
    private final List<CompareItem<?>> differences;
    private final List<CompareItem<?>> sames;
    private final transient Map<String, CompareItem> resultMap;

    public CompareResult(L left, R right) {
        this.left = left;
        this.right = right;
        this.differences = Lists.newArrayList();
        this.sames = Lists.newArrayList();
        this.resultMap = Maps.newHashMap();
    }

    public L getLeft() {
        return left;
    }

    public R getRight() {
        return right;
    }

    public List<CompareItem<?>> getDifferences() {
        return differences;
    }

    public List<CompareItem<?>> getSames() {
        return sames;
    }

    public <T> void addDifference(T firstVal, T secondVal, ComparePair<L, R, T> comparePair) {
        addItem(new CompareItem<>(firstVal, secondVal, comparePair, true));
    }

    public <T> void addItem(T firstVal, T secondVal, ComparePair<L, R, T> comparePair, boolean isDifferent) {
        addItem(new CompareItem<>(firstVal, secondVal, comparePair, isDifferent ? true : false));
    }

    public <T> void addItem(CompareItem<T> compareItem) {
        if(compareItem.isDiff()) {
            differences.add(compareItem);
        } else {
            sames.add(compareItem);
        }
        resultMap.put(compareItem.getName(), compareItem);
    }

    public <T> CompareItem<T> getItem(String name) {
        return resultMap.get(name);
    }

    public <T> CompareItem<T> getItem(ComparePair pair) {
        if(pair == null) {
            return null;
        }
        return resultMap.get(pair.getName());
    }

    public <T> CompareItem<T> removeItem(String name) {
        CompareItem compareItem = resultMap.get(name);
        removeItem(compareItem);
        return compareItem;
    }

    public void removeItem(CompareItem<?> compareItem) {
        if(compareItem == null) {
            return;
        }
        if(compareItem.isDiff) {
            differences.remove(compareItem);
        } else {
            sames.remove(compareItem);
        }
        resultMap.remove(compareItem.getName());
    }

    public boolean isDifferent() {
        return differences.size() > 0;
    }

    @Override
    public String toString() {
        return "CompareResult{" +
                "left=" + left +
                ", right=" + right +
                ", differences=" + differences +
                ", sames=" + sames +
                '}';
    }

    public class CompareItem<V> {
        private final V leftVal;
        private final V rightVal;
        private final ComparePair<L, R, V> comparePair;
        private final boolean isDiff;

        public CompareItem(V leftVal, V rightVal, ComparePair<L, R, V> comparePair, boolean isDiff) {
            this.leftVal = leftVal;
            this.rightVal = rightVal;
            this.comparePair = comparePair;
            this.isDiff = isDiff;
        }

        public Object getLeftVal() {
            return leftVal;
        }

        public Object getRightVal() {
            return rightVal;
        }

        public ComparePair<L, R, V> getComparePair() {
            return comparePair;
        }

        public boolean isDiff() {
            return isDiff;
        }

        public String getName() {
            return comparePair.getName();
        }

        public void ifDifferent(BiConsumer<V, V> consumer) {
            if(isDiff) {
                consumer.accept(leftVal, rightVal);
            }
        }

        public void recompareWith(BiPredicate<V, V> comparer) {
            V leftVal = comparePair.getLeft().apply(left);
            V rightVal = comparePair.getRight().apply(right);
            boolean testResult = comparer.test(leftVal, rightVal);
            CompareItem<V> newItem = new CompareItem<>(leftVal, rightVal, comparePair.copyOf(comparer), !testResult);
            removeItem(this);
            addItem(newItem);
        }

        public void recompare() {
            recompareWith(comparePair.getComparer());
        }

        @Override
        public String toString() {
            return "CompareItem{" +
                    "leftVal=" + getValueDescription(leftVal) +
                    ", rightVal=" + getValueDescription(rightVal) +
                    ", comparePair=" + comparePair +
                    ", isDiff=" + isDiff +
                    '}';
        }

        private String getValueDescription(Object o) {
            if(o == null) {
                return "null";
            }
            return o + "(" + o.getClass().getName() +")";
        }

    }

}
